/**
** BIOS Routine by Neil C. Obremski                           [ Magenta's Maze ]
** 2017 Gibdon Moon Productions                   [ http://magsmaze.gibdon.com ]
**
** This is free and unencumbered software released into the public domain.
**
** Anyone is free to copy, modify, publish, use, compile, sell, or
** distribute this software, either in source code form or as a compiled
** binary, for any purpose, commercial or non-commercial, and by any
** means.
**
** In jurisdictions that recognize copyright laws, the author or authors
** of this software dedicate any and all copyright interest in the
** software to the public domain. We make this dedication for the benefit
** of the public at large and to the detriment of our heirs and
** successors. We intend this dedication to be an overt act of
** relinquishment in perpetuity of all present and future rights to this
** software under copyright law.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
** OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
** OTHER DEALINGS IN THE SOFTWARE.
**
** Tested w/ DOSBox 0.74, Microsoft C 5.10
 */
#ifndef __MAG_BIOS
#define __MAG_BIOS

/* DETECT C COMPILER */
#if defined(__WATCOMC__)
#define MAG_C ("OpenWatcom ")

#elif defined(__TURBOC__)
#define MAG_C "Turbo C 2.01"
#include <alloc.h>
void far  *_Cdecl farmalloc	(unsigned long nbytes);
#define _fmalloc farmalloc
#define _bios_keybrd bioskey
#define _KEYBRD_READ 0
#define _KEYBRD_READY 1
#define _KEYBRD_SHIFTSTATUS 2
#define _disable disable
#define _enable enable

#elif defined(__POWERC)
#define MAG_C "Power C"
#define _bios_keybrd bioskey
#define _KEYBRD_READ 0
#define _KEYBRD_READY 1
#define _KEYBRD_SHIFTSTATUS 2
#define _disable disable
#define _enable enable

#elif defined(MSDOS) && defined(M_I86) /* Microsoft C defines these */
#define MAG_C "Microsoft C 5.10"
#include <malloc.h>

#else
#error UNRECOGNIZED C COMPILER (updated mag-c.h w/ info)
#endif

#include <conio.h>	/* inp, outp */
#include <bios.h>	/* REGS, int86 */
#include <dos.h>	/* REGS, int86 */
#include <string.h>	/* memset() */


/* default (AT) typematic rate/delay (10.0cps / 500ms) */
#define TYPE_DELAY_DEFAULT (unsigned char)0x01
#define TYPE_REPEAT_DEFAULT (unsigned char)0x0C

/* slow typematic rate/delay (2.0cps / 1000ms) */
#define TYPE_DELAY_SLOW (unsigned char)0x03
#define TYPE_REPEAT_SLOW (unsigned char)0x1F

unsigned char far *bioschar(unsigned char);
void breath(void);
char has8087(void);
void locate(int,int);
unsigned long microsec(char);
char pcjr(void);
unsigned int pitwait(unsigned int, unsigned int *);
int screen(int);
char tandy1000(void);
#define ticker() (*((unsigned long far*)0x46CL)) /* default = ~1/18.2 sec */
void typematic(unsigned char, unsigned char);
void wait(int);

#define FONT_BIT(p,x,y) ((*((p)+(y))) >> (7 - (x)) & 0x1)

/* returns start address of bios character glyph (each one is 8 bytes) */
unsigned char far *bioschar(unsigned char c)
{
	/* INT 0x1F refers to a block of memory for codes 128+ */
	static unsigned long far *vector1f = (unsigned long far*)(0x1f * 4);

	/* F000:FA6E contains first 127 ASCII characters */
	unsigned char far *p = (unsigned char far*)0xF000FA6E;

	if (252 == c) { /* patch for ^3 superscript */
		static unsigned char superscript3[8] = {
			0x70, 0x08, 0x30, 0x08, 0x70, 0x00, 0x00, 0x00 };
		return (unsigned char far *)superscript3;
	}

	/* upper ASCII (128 - 255) retrieved with INT 1F vector */
	if (c > 127) {
		p = (unsigned char far*)(*vector1f);
		c -= 128;
	}

	return p + (((int)c) << 3);
}

/* let the CPU breath */
void breath(void)
{
	union REGS reg_in, reg_out;
	static char v = -1; /* DOS version (-1=?, 0=DOS 1, 1=DOS 2+) */

	if (v == -1) {
		memset(&reg_in, 0, sizeof(reg_in));
		reg_in.x.ax = 0x2b01;
		reg_in.x.cx = 0x4445;
		reg_in.x.dx = 0x5351;
		intdos(&reg_in, &reg_out);
		if (reg_out.h.al != 0xff) {
			v = 1;
		} else {
			v = 0; /* DOS 1.x did not support this so it's 0 */
		}
	}

	memset(&reg_in, 0, sizeof(reg_in));
	int86(0x28, &reg_in, &reg_out); /* INT 28h: DOS Idle Handler */

	if (v) { /* AT Extended Services */
		memset(&reg_in, 0, sizeof(reg_in));
		reg_in.x.ax = 0x1000;
		int86(0x15, &reg_in, &reg_out);
	}
}

char *exefn(char *argv0, char *cfn)
{
	static char fn[13] = { '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
		'\0', '\0', '\0', '\0' };
	char *rp = argv0 ? argv0 : cfn, *wp = fn; /* read/write pointers */

	if (fn[0] && !argv0 && !cfn) { /* return cached string */
		return fn;
	}

	if (argv0 && strcmp(argv0, "C")) { /* use argv[0] */
		rp += strlen(argv0);
		while (--rp > argv0) {
			if ('\\' == *rp) {
				rp++;
				break;
			}
		}
		strncpy(fn, rp, 13);
		return fn;
	}

	/* DOS 2.x on Tandy 1000 only has "C" in argv[0] so we use __FILE__ ... */
	rp = cfn ? cfn : __FILE__;

	for ( ; *rp && wp < fn + 13; wp++, rp++) {
		*wp = *rp;
		if ('.' == *wp) {
			strncpy(wp+1, "EXE", wp - fn);
			break;
		}
	}

	return fn;
}

/* returns 1 if 80x87 coprocessor present in system (fast[er] floating point) */
char has8087(void)
{
	union REGS reg;
	memset(&reg, 0, sizeof(reg));
	int86(0x11, &reg, &reg);
	return (char)(reg.x.ax & 0x2);
}

/* moves the cursor to a text position on the screen */
void locate(int row, int col)
{
	union REGS reg;
	memset(&reg, 0, sizeof(reg));
	reg.h.ah = 0x02;
	reg.h.bh = 0x00; /* display page */
	reg.h.dh = (unsigned char)row;
	reg.h.dl = (unsigned char)col;
	int86(0x10, &reg, &reg);
}

/* returns microseconds past midnight (thanks:uclock.c, Bob Stout / Snippets) */
unsigned long microsec(char restart)
{
	static char init = 0;
	unsigned char msb, lsb;
	unsigned int pit_ticks;
	static unsigned long last, init_count;
	unsigned long count, us_tmp;

	if (!init) { /* initialize timing data */
		_disable();
		outp(0x43, 0x36); outp(0x40, 0); outp(0x40, 0);
		init_count = ticker();
		_enable();
		init = 1;
		return 0;
	}

	/* read current timing data from PIT#0 and BIOS tick count */
	_disable();
	outp(0x43, 0);
	lsb = (unsigned char)inp(0x40);
	msb = (unsigned char)inp(0x40);
	count = ticker();
	_enable();

	if (restart) {
		init_count = count;
	}

	/* truncate based on initial (restarted) timer count */
	if (count < init_count) {
		count += last;
	} else {
		last = count;
	}
	count -= init_count;

	/* merge timing data into microseconds */
	pit_ticks = (unsigned)(-1)-((msb<<8)|lsb);	/* reversed/elapsed PIT count */
	us_tmp = count * 54925;						/* microseconds per BIOS tick */
	return (us_tmp + ((long)pit_ticks * 8381	/* (4/4.77 MHz) */
		+ us_tmp % 10000) / 10000);				/* scale for timer ticks */
}

/* returns 1 if this computer is a PC jr */
char pcjr(void)
{
	/* http://www.vcfed.org/forum/showthread.php?32447-Tandy-1000-Detection */
	unsigned char far *p = (unsigned char far*)(0xFFFF000E);
	return (char)((*p) == 0xFD);
}

/* wait for the specified amount of PIT#0 counter to elapse */
unsigned int pitwait(unsigned int counter, unsigned int *pdiff)
{
	register unsigned int t, start, loops = 0;

	_disable();

	outp(0x43, 0); /* send latch count command to PIT */

	start = (unsigned)inp(0x40);
	start |= ((unsigned)inp(0x40)) << 8;
	t = start;

	for (loops = 0; loops < 0xFFFF; loops++) {
		t = (unsigned)inp(0x40);
		t |= ((unsigned)inp(0x40)) << 8;

		if (start - t >= counter) {
			break;
		}
	}

	if (pdiff) {
		*pdiff = start - t;
	}

	_enable();

	return loops; /* the closer this is to the request, faster the computer */
}

int screen(int mode)
{
	int oldmode;
	union REGS reg;
	memset(&reg, 0, sizeof(reg));

	/* get old mode */
	reg.h.ah = 0x0F;
	int86(0x10, &reg, &reg);
	oldmode = reg.h.al;

	/* set new mode */
	if (oldmode != mode) {
		reg.h.ah = 0;
		reg.h.al = (unsigned char)mode;
		int86(0x10, &reg, &reg);
	}

	return oldmode;
}

/* returns 1 if this computer is a Tandy 1000 */
char tandy1000(void)
{
	/* http://www.vcfed.org/forum/showthread.php?32447-Tandy-1000-Detection */
	unsigned char far *p = (unsigned char far*)(0xFFFF000EL);
	if ((*p) != 0xFF) {
		return 0;
	}

	p = (unsigned char far*)(0xFC000000L);
	if ((*p) != 0x21) {
		return 0;
	}

	return 1;
}

/* set typematic keyboard delay (00-03h) and repeat rate (00-1Fh) */
void typematic(unsigned char delay, unsigned char repeat)
{
	union REGS reg;

	if (tandy1000() || pcjr()) {
		return; /* ignore */
	}

	memset(&reg, 0, sizeof(reg));
	reg.h.ah = 0x03;
	reg.h.al = 0x05;
	reg.h.bh = delay;	/* 00h = 250ms to 03h = 1000ms */
	reg.h.bl = repeat;	/* 00h=30/sec to 0Ch=10/sec to 1Fh=2/sec */
	int86(0x16, &reg, &reg);
}

/* rough wait function that causes processor to spin until ticks elapse */
void wait(int ticks)
{
	unsigned long curtick = ticker();
	unsigned long endtick = curtick + (unsigned long)ticks;
	register unsigned int pitcount; /* sub-tick count */

	outp(0x43, 0);
	pitcount = (unsigned)inp(0x40);
	pitcount |= ((unsigned)inp(0x40)) << 8;
	pitcount = 0xFFFF - pitcount;

	if (pitcount > 0x1000 && pitcount < 0x7000) {
		endtick--;
	} else {
		pitcount = 0;
	}

	while (curtick < endtick) {
		curtick = ticker();
		breath();
	}

	if (pitcount) { /* sub-tick delay */
		pitwait(pitcount, 0);
	}
}

#endif
